/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"FrameAnimator.h"#include"mozilla/MemoryReporting.h"#include"mozilla/Move.h"#include"mozilla/CheckedInt.h"#include"imgIContainer.h"#include"LookupResult.h"#include"MainThreadUtils.h"#include"RasterImage.h"#include"gfxPrefs.h"#include"pixman.h"#include<algorithm>namespacemozilla{usingnamespacegfx;namespaceimage{///////////////////////////////////////////////////////////////////////////////// AnimationState implementation.///////////////////////////////////////////////////////////////////////////////constgfx::IntRectAnimationState::UpdateState(boolaAnimationFinished,RasterImage*aImage,constgfx::IntSize&aSize,boolaAllowInvalidation/* = true */){LookupResultresult=SurfaceCache::Lookup(ImageKey(aImage),RasterSurfaceKey(aSize,DefaultSurfaceFlags(),PlaybackType::eAnimated));returnUpdateStateInternal(result,aAnimationFinished,aSize,aAllowInvalidation);}constgfx::IntRectAnimationState::UpdateStateInternal(LookupResult&aResult,boolaAnimationFinished,constgfx::IntSize&aSize,boolaAllowInvalidation/* = true */){// Update mDiscarded and mIsCurrentlyDecoded.if(aResult.Type()==MatchType::NOT_FOUND){// no frames, we've either been discarded, or never been decoded before.mDiscarded=mHasBeenDecoded;mIsCurrentlyDecoded=false;}elseif(aResult.Type()==MatchType::PENDING){// no frames yet, but a decoder is or will be working on it.mDiscarded=false;mIsCurrentlyDecoded=false;}else{MOZ_ASSERT(aResult.Type()==MatchType::EXACT);mDiscarded=false;// If mHasBeenDecoded is true then we know the true total frame count and// we can use it to determine if we have all the frames now so we know if// we are currently fully decoded.// If mHasBeenDecoded is false then we'll get another UpdateState call// when the decode finishes.if(mHasBeenDecoded){Maybe<uint32_t>frameCount=FrameCount();MOZ_ASSERT(frameCount.isSome());if(NS_SUCCEEDED(aResult.Surface().Seek(*frameCount-1))&&aResult.Surface()->IsFinished()){mIsCurrentlyDecoded=true;}else{mIsCurrentlyDecoded=false;}}}gfx::IntRectret;if(aAllowInvalidation){// Update the value of mCompositedFrameInvalid.if(mIsCurrentlyDecoded||aAnimationFinished){// Animated images that have finished their animation (ie because it is a// finite length animation) don't have RequestRefresh called on them, and so// mCompositedFrameInvalid would never get cleared. We clear it here (and// also in RasterImage::Decode when we create a decoder for an image that// has finished animated so it can display sooner than waiting until the// decode completes). We also do it if we are fully decoded. This is safe// to do for images that aren't finished animating because before we paint// the refresh driver will call into us to advance to the correct frame,// and that will succeed because we have all the frames.if(mCompositedFrameInvalid){// Invalidate if we are marking the composited frame valid.ret.SizeTo(aSize);}mCompositedFrameInvalid=false;}elseif(aResult.Type()==MatchType::NOT_FOUND||aResult.Type()==MatchType::PENDING){if(mHasBeenDecoded){MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());mCompositedFrameInvalid=true;}}// Otherwise don't change the value of mCompositedFrameInvalid, it will be// updated by RequestRefresh.}returnret;}voidAnimationState::NotifyDecodeComplete(){mHasBeenDecoded=true;}voidAnimationState::ResetAnimation(){mCurrentAnimationFrameIndex=0;}voidAnimationState::SetAnimationMode(uint16_taAnimationMode){mAnimationMode=aAnimationMode;}voidAnimationState::UpdateKnownFrameCount(uint32_taFrameCount){if(aFrameCount<=mFrameCount){// Nothing to do. Since we can redecode animated images, we may see the same// sequence of updates replayed again, so seeing a smaller frame count than// what we already know about doesn't indicate an error.return;}MOZ_ASSERT(!mHasBeenDecoded,"Adding new frames after decoding is finished?");MOZ_ASSERT(aFrameCount<=mFrameCount+1,"Skipped a frame?");mFrameCount=aFrameCount;}Maybe<uint32_t>AnimationState::FrameCount()const{returnmHasBeenDecoded?Some(mFrameCount):Nothing();}voidAnimationState::SetFirstFrameRefreshArea(constIntRect&aRefreshArea){mFirstFrameRefreshArea=aRefreshArea;}voidAnimationState::InitAnimationFrameTimeIfNecessary(){if(mCurrentAnimationFrameTime.IsNull()){mCurrentAnimationFrameTime=TimeStamp::Now();}}voidAnimationState::SetAnimationFrameTime(constTimeStamp&aTime){mCurrentAnimationFrameTime=aTime;}uint32_tAnimationState::GetCurrentAnimationFrameIndex()const{returnmCurrentAnimationFrameIndex;}FrameTimeoutAnimationState::LoopLength()const{// If we don't know the loop length yet, we have to treat it as infinite.if(!mLoopLength){returnFrameTimeout::Forever();}MOZ_ASSERT(mHasBeenDecoded,"We know the loop length but decoding isn't done?");// If we're not looping, a single loop time has no meaning.if(mAnimationMode!=imgIContainer::kNormalAnimMode){returnFrameTimeout::Forever();}return*mLoopLength;}///////////////////////////////////////////////////////////////////////////////// FrameAnimator implementation.///////////////////////////////////////////////////////////////////////////////Maybe<TimeStamp>FrameAnimator::GetCurrentImgFrameEndTime(AnimationState&aState,DrawableSurface&aFrames)const{TimeStampcurrentFrameTime=aState.mCurrentAnimationFrameTime;Maybe<FrameTimeout>timeout=GetTimeoutForFrame(aState,aFrames,aState.mCurrentAnimationFrameIndex);if(timeout.isNothing()){MOZ_ASSERT(aState.GetHasBeenDecoded()&&!aState.GetIsCurrentlyDecoded());returnNothing();}if(*timeout==FrameTimeout::Forever()){// We need to return a sentinel value in this case, because our logic// doesn't work correctly if we have an infinitely long timeout. We use one// year in the future as the sentinel because it works with the loop in// RequestRefresh() below.// XXX(seth): It'd be preferable to make our logic work correctly with// infinitely long timeouts.returnSome(TimeStamp::NowLoRes()+TimeDuration::FromMilliseconds(31536000.0));}TimeDurationdurationOfTimeout=TimeDuration::FromMilliseconds(double(timeout->AsMilliseconds()));TimeStampcurrentFrameEndTime=currentFrameTime+durationOfTimeout;returnSome(currentFrameEndTime);}RefreshResultFrameAnimator::AdvanceFrame(AnimationState&aState,DrawableSurface&aFrames,TimeStampaTime){NS_ASSERTION(aTime<=TimeStamp::Now(),"Given time appears to be in the future");AUTO_PROFILER_LABEL("FrameAnimator::AdvanceFrame",GRAPHICS);RefreshResultret;// Determine what the next frame is, taking into account looping.uint32_tcurrentFrameIndex=aState.mCurrentAnimationFrameIndex;uint32_tnextFrameIndex=currentFrameIndex+1;// Check if we're at the end of the loop. (FrameCount() returns Nothing() if// we don't know the total count yet.)if(aState.FrameCount()==Some(nextFrameIndex)){// If we are not looping forever, initialize the loop counterif(aState.mLoopRemainingCount<0&&aState.LoopCount()>=0){aState.mLoopRemainingCount=aState.LoopCount();}// If animation mode is "loop once", or we're at end of loop counter,// it's time to stop animating.if(aState.mAnimationMode==imgIContainer::kLoopOnceAnimMode||aState.mLoopRemainingCount==0){ret.mAnimationFinished=true;}nextFrameIndex=0;if(aState.mLoopRemainingCount>0){aState.mLoopRemainingCount--;}// If we're done, exit early.if(ret.mAnimationFinished){returnret;}}if(nextFrameIndex>=aState.KnownFrameCount()){// We've already advanced to the last decoded frame, nothing more we can do.// We're blocked by network/decoding from displaying the animation at the// rate specified, so that means the frame we are displaying (the latest// available) is the frame we want to be displaying at this time. So we// update the current animation time. If we didn't update the current// animation time then it could lag behind, which would indicate that we are// behind in the animation and should try to catch up. When we are done// decoding (and thus can loop around back to the start of the animation) we// would then jump to a random point in the animation to try to catch up.// But we were never behind in the animation.aState.mCurrentAnimationFrameTime=aTime;returnret;}// There can be frames in the surface cache with index >= KnownFrameCount()// which GetRawFrame() can access because an async decoder has decoded them,// but which AnimationState doesn't know about yet because we haven't received// the appropriate notification on the main thread. Make sure we stay in sync// with AnimationState.MOZ_ASSERT(nextFrameIndex<aState.KnownFrameCount());RawAccessFrameRefnextFrame=GetRawFrame(aFrames,nextFrameIndex);// We should always check to see if we have the next frame even if we have// previously finished decoding. If we needed to redecode (e.g. due to a draw// failure) we would have discarded all the old frames and may not yet have// the new ones.if(!nextFrame||!nextFrame->IsFinished()){// Uh oh, the frame we want to show is currently being decoded (partial)// Wait until the next refresh driver tick and try againreturnret;}Maybe<FrameTimeout>nextFrameTimeout=GetTimeoutForFrame(aState,aFrames,nextFrameIndex);// GetTimeoutForFrame can only return none if frame doesn't exist,// but we just got it above.MOZ_ASSERT(nextFrameTimeout.isSome());if(*nextFrameTimeout==FrameTimeout::Forever()){ret.mAnimationFinished=true;}if(nextFrameIndex==0){ret.mDirtyRect=aState.FirstFrameRefreshArea();}else{MOZ_ASSERT(nextFrameIndex==currentFrameIndex+1);// Change frameif(!DoBlend(aFrames,&ret.mDirtyRect,currentFrameIndex,nextFrameIndex)){// something went wrong, move on to nextNS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");nextFrame->SetCompositingFailed(true);Maybe<TimeStamp>currentFrameEndTime=GetCurrentImgFrameEndTime(aState,aFrames);MOZ_ASSERT(currentFrameEndTime.isSome());aState.mCurrentAnimationFrameTime=*currentFrameEndTime;aState.mCurrentAnimationFrameIndex=nextFrameIndex;returnret;}nextFrame->SetCompositingFailed(false);}Maybe<TimeStamp>currentFrameEndTime=GetCurrentImgFrameEndTime(aState,aFrames);MOZ_ASSERT(currentFrameEndTime.isSome());aState.mCurrentAnimationFrameTime=*currentFrameEndTime;// If we can get closer to the current time by a multiple of the image's loop// time, we should. We can only do this if we're done decoding; otherwise, we// don't know the full loop length, and LoopLength() will have to return// FrameTimeout::Forever(). We also skip this for images with a finite loop// count if we have initialized mLoopRemainingCount (it only gets initialized// after one full loop).FrameTimeoutloopTime=aState.LoopLength();if(loopTime!=FrameTimeout::Forever()&&(aState.LoopCount()<0||aState.mLoopRemainingCount>=0)){TimeDurationdelay=aTime-aState.mCurrentAnimationFrameTime;if(delay.ToMilliseconds()>loopTime.AsMilliseconds()){// Explicitly use integer division to get the floor of the number of// loops.uint64_tloops=static_cast<uint64_t>(delay.ToMilliseconds())/loopTime.AsMilliseconds();// If we have a finite loop count limit the number of loops we advance.if(aState.mLoopRemainingCount>=0){MOZ_ASSERT(aState.LoopCount()>=0);loops=std::min(loops,CheckedUint64(aState.mLoopRemainingCount).value());}aState.mCurrentAnimationFrameTime+=TimeDuration::FromMilliseconds(loops*loopTime.AsMilliseconds());if(aState.mLoopRemainingCount>=0){MOZ_ASSERT(loops<=CheckedUint64(aState.mLoopRemainingCount).value());aState.mLoopRemainingCount-=CheckedInt32(loops).value();}}}// Set currentAnimationFrameIndex at the last possible momentaState.mCurrentAnimationFrameIndex=nextFrameIndex;// If we're here, we successfully advanced the frame.ret.mFrameAdvanced=true;returnret;}RefreshResultFrameAnimator::RequestRefresh(AnimationState&aState,constTimeStamp&aTime,boolaAnimationFinished){// By default, an empty RefreshResult.RefreshResultret;if(aState.IsDiscarded()){returnret;}// Get the animation frames once now, and pass them down to callees because// the surface could be discarded at anytime on a different thread. This is// must easier to reason about then trying to write code that is safe to// having the surface disappear at anytime.LookupResultresult=SurfaceCache::Lookup(ImageKey(mImage),RasterSurfaceKey(mSize,DefaultSurfaceFlags(),PlaybackType::eAnimated));ret.mDirtyRect=aState.UpdateStateInternal(result,aAnimationFinished,mSize);if(aState.IsDiscarded()||!result){if(!ret.mDirtyRect.IsEmpty()){ret.mFrameAdvanced=true;}returnret;}// only advance the frame if the current time is greater than or// equal to the current frame's end time.Maybe<TimeStamp>currentFrameEndTime=GetCurrentImgFrameEndTime(aState,result.Surface());if(currentFrameEndTime.isNothing()){MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());MOZ_ASSERT(aState.GetHasBeenDecoded()&&!aState.GetIsCurrentlyDecoded());MOZ_ASSERT(aState.mCompositedFrameInvalid);// Nothing we can do but wait for our previous current frame to be decoded// again so we can determine what to do next.returnret;}while(*currentFrameEndTime<=aTime){TimeStampoldFrameEndTime=*currentFrameEndTime;RefreshResultframeRes=AdvanceFrame(aState,result.Surface(),aTime);// Accumulate our result for returning to callers.ret.Accumulate(frameRes);currentFrameEndTime=GetCurrentImgFrameEndTime(aState,result.Surface());// AdvanceFrame can't advance to a frame that doesn't exist yet.MOZ_ASSERT(currentFrameEndTime.isSome());// If we didn't advance a frame, and our frame end time didn't change,// then we need to break out of this loop & wait for the frame(s)// to finish downloading.if(!frameRes.mFrameAdvanced&&(*currentFrameEndTime==oldFrameEndTime)){break;}}// Advanced to the correct frame, the composited frame is now valid to be drawn.if(*currentFrameEndTime>aTime){aState.mCompositedFrameInvalid=false;ret.mDirtyRect=IntRect(IntPoint(0,0),mSize);}MOZ_ASSERT(!aState.mIsCurrentlyDecoded||!aState.mCompositedFrameInvalid);returnret;}LookupResultFrameAnimator::GetCompositedFrame(AnimationState&aState){LookupResultresult=SurfaceCache::Lookup(ImageKey(mImage),RasterSurfaceKey(mSize,DefaultSurfaceFlags(),PlaybackType::eAnimated));if(aState.mCompositedFrameInvalid){MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());MOZ_ASSERT(aState.GetHasBeenDecoded());MOZ_ASSERT(!aState.GetIsCurrentlyDecoded());if(result.Type()==MatchType::NOT_FOUND){returnresult;}returnLookupResult(MatchType::PENDING);}// If we have a composited version of this frame, return that.if(mLastCompositedFrameIndex>=0&&(uint32_t(mLastCompositedFrameIndex)==aState.mCurrentAnimationFrameIndex)){returnLookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),MatchType::EXACT);}// Otherwise return the raw frame. DoBlend is required to ensure that we only// hit this case if the frame is not paletted and doesn't require compositing.if(!result){returnresult;}// Seek to the appropriate frame. If seeking fails, it means that we couldn't// get the frame we're looking for; treat this as if the lookup failed.if(NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))){if(result.Type()==MatchType::NOT_FOUND){returnresult;}returnLookupResult(MatchType::PENDING);}MOZ_ASSERT(!result.Surface()->GetIsPaletted(),"About to return a paletted frame");returnresult;}Maybe<FrameTimeout>FrameAnimator::GetTimeoutForFrame(AnimationState&aState,DrawableSurface&aFrames,uint32_taFrameNum)const{RawAccessFrameRefframe=GetRawFrame(aFrames,aFrameNum);if(frame){AnimationDatadata=frame->GetAnimationData();returnSome(data.mTimeout);}MOZ_ASSERT(aState.mHasBeenDecoded&&!aState.mIsCurrentlyDecoded);returnNothing();}staticvoidDoCollectSizeOfCompositingSurfaces(constRawAccessFrameRef&aSurface,SurfaceMemoryCounterTypeaType,nsTArray<SurfaceMemoryCounter>&aCounters,MallocSizeOfaMallocSizeOf){// Concoct a SurfaceKey for this surface.SurfaceKeykey=RasterSurfaceKey(aSurface->GetImageSize(),DefaultSurfaceFlags(),PlaybackType::eStatic);// Create a counter for this surface.SurfaceMemoryCountercounter(key,/* aIsLocked = */true,aType);// Extract the surface's memory usage information.size_theap=0,nonHeap=0,handles=0;aSurface->AddSizeOfExcludingThis(aMallocSizeOf,heap,nonHeap,handles);counter.Values().SetDecodedHeap(heap);counter.Values().SetDecodedNonHeap(nonHeap);counter.Values().SetSharedHandles(handles);// Record it.aCounters.AppendElement(counter);}voidFrameAnimator::CollectSizeOfCompositingSurfaces(nsTArray<SurfaceMemoryCounter>&aCounters,MallocSizeOfaMallocSizeOf)const{if(mCompositingFrame){DoCollectSizeOfCompositingSurfaces(mCompositingFrame,SurfaceMemoryCounterType::COMPOSITING,aCounters,aMallocSizeOf);}if(mCompositingPrevFrame){DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame,SurfaceMemoryCounterType::COMPOSITING_PREV,aCounters,aMallocSizeOf);}}RawAccessFrameRefFrameAnimator::GetRawFrame(DrawableSurface&aFrames,uint32_taFrameNum)const{// Seek to the frame we want. If seeking fails, it means we couldn't get the// frame we're looking for, so we bail here to avoid returning the wrong frame// to the caller.if(NS_FAILED(aFrames.Seek(aFrameNum))){returnRawAccessFrameRef();// Not available yet.}returnaFrames->RawAccessRef();}//******************************************************************************// DoBlend gets called when the timer for animation get fired and we have to// update the composited frame of the animation.boolFrameAnimator::DoBlend(DrawableSurface&aFrames,IntRect*aDirtyRect,uint32_taPrevFrameIndex,uint32_taNextFrameIndex){RawAccessFrameRefprevFrame=GetRawFrame(aFrames,aPrevFrameIndex);RawAccessFrameRefnextFrame=GetRawFrame(aFrames,aNextFrameIndex);MOZ_ASSERT(prevFrame&&nextFrame,"Should have frames here");AnimationDataprevFrameData=prevFrame->GetAnimationData();if(prevFrameData.mDisposalMethod==DisposalMethod::RESTORE_PREVIOUS&&!mCompositingPrevFrame){prevFrameData.mDisposalMethod=DisposalMethod::CLEAR;}IntRectprevRect=prevFrameData.mBlendRect?prevFrameData.mRect.Intersect(*prevFrameData.mBlendRect):prevFrameData.mRect;boolisFullPrevFrame=prevRect.x==0&&prevRect.y==0&&prevRect.width==mSize.width&&prevRect.height==mSize.height;// Optimization: DisposeClearAll if the previous frame is the same size as// container and it's clearing itselfif(isFullPrevFrame&&(prevFrameData.mDisposalMethod==DisposalMethod::CLEAR)){prevFrameData.mDisposalMethod=DisposalMethod::CLEAR_ALL;}AnimationDatanextFrameData=nextFrame->GetAnimationData();IntRectnextRect=nextFrameData.mBlendRect?nextFrameData.mRect.Intersect(*nextFrameData.mBlendRect):nextFrameData.mRect;boolisFullNextFrame=nextRect.x==0&&nextRect.y==0&&nextRect.width==mSize.width&&nextRect.height==mSize.height;if(!nextFrame->GetIsPaletted()){// Optimization: Skip compositing if the previous frame wants to clear the// whole imageif(prevFrameData.mDisposalMethod==DisposalMethod::CLEAR_ALL){aDirtyRect->SetRect(0,0,mSize.width,mSize.height);returntrue;}// Optimization: Skip compositing if this frame is the same size as the// container and it's fully drawing over prev frame (no alpha)if(isFullNextFrame&&(nextFrameData.mDisposalMethod!=DisposalMethod::RESTORE_PREVIOUS)&&!nextFrameData.mHasAlpha){aDirtyRect->SetRect(0,0,mSize.width,mSize.height);returntrue;}}// Calculate area that needs updatingswitch(prevFrameData.mDisposalMethod){default:MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");caseDisposalMethod::NOT_SPECIFIED:caseDisposalMethod::KEEP:*aDirtyRect=nextRect;break;caseDisposalMethod::CLEAR_ALL:// Whole image container is clearedaDirtyRect->SetRect(0,0,mSize.width,mSize.height);break;caseDisposalMethod::CLEAR:// Calc area that needs to be redrawn (the combination of previous and// this frame)// XXX - This could be done with multiple framechanged calls// Having prevFrame way at the top of the image, and nextFrame// way at the bottom, and both frames being small, we'd be// telling framechanged to refresh the whole image when only two// small areas are needed.aDirtyRect->UnionRect(nextRect,prevRect);break;caseDisposalMethod::RESTORE_PREVIOUS:aDirtyRect->SetRect(0,0,mSize.width,mSize.height);break;}// Optimization:// Skip compositing if the last composited frame is this frame// (Only one composited frame was made for this animation. Example:// Only Frame 3 of a 10 frame image required us to build a composite frame// On the second loop, we do not need to rebuild the frame// since it's still sitting in compositingFrame)if(mLastCompositedFrameIndex==int32_t(aNextFrameIndex)){returntrue;}boolneedToBlankComposite=false;// Create the Compositing Frameif(!mCompositingFrame){RefPtr<imgFrame>newFrame=newimgFrame;nsresultrv=newFrame->InitForAnimator(mSize,SurfaceFormat::B8G8R8A8);if(NS_FAILED(rv)){mCompositingFrame.reset();returnfalse;}mCompositingFrame=newFrame->RawAccessRef();needToBlankComposite=true;}elseif(int32_t(aNextFrameIndex)!=mLastCompositedFrameIndex+1){// If we are not drawing on top of last composited frame,// then we are building a new composite frame, so let's clear it first.needToBlankComposite=true;}AnimationDatacompositingFrameData=mCompositingFrame->GetAnimationData();// More optimizations possible when next frame is not transparent// But if the next frame has DisposalMethod::RESTORE_PREVIOUS,// this "no disposal" optimization is not possible,// because the frame in "after disposal operation" state// needs to be stored in compositingFrame, so it can be// copied into compositingPrevFrame later.booldoDisposal=true;if(!nextFrameData.mHasAlpha&&nextFrameData.mDisposalMethod!=DisposalMethod::RESTORE_PREVIOUS){if(isFullNextFrame){// Optimization: No need to dispose prev.frame when// next frame is full frame and not transparent.doDisposal=false;// No need to blank the composite frameneedToBlankComposite=false;}else{if((prevRect.x>=nextRect.x)&&(prevRect.y>=nextRect.y)&&(prevRect.x+prevRect.width<=nextRect.x+nextRect.width)&&(prevRect.y+prevRect.height<=nextRect.y+nextRect.height)){// Optimization: No need to dispose prev.frame when// next frame fully overlaps previous frame.doDisposal=false;}}}if(doDisposal){// Dispose of previous: clear, restore, or keep (copy)switch(prevFrameData.mDisposalMethod){caseDisposalMethod::CLEAR:if(needToBlankComposite){// If we just created the composite, it could have anything in its// buffer. Clear whole frameClearFrame(compositingFrameData.mRawData,compositingFrameData.mRect);}else{// Only blank out previous frame area (both color & Mask/Alpha)ClearFrame(compositingFrameData.mRawData,compositingFrameData.mRect,prevRect);}break;caseDisposalMethod::CLEAR_ALL:ClearFrame(compositingFrameData.mRawData,compositingFrameData.mRect);break;caseDisposalMethod::RESTORE_PREVIOUS:// It would be better to copy only the area changed back to// compositingFrame.if(mCompositingPrevFrame){AnimationDatacompositingPrevFrameData=mCompositingPrevFrame->GetAnimationData();CopyFrameImage(compositingPrevFrameData.mRawData,compositingPrevFrameData.mRect,compositingFrameData.mRawData,compositingFrameData.mRect);// destroy only if we don't need it for this frame's disposalif(nextFrameData.mDisposalMethod!=DisposalMethod::RESTORE_PREVIOUS){mCompositingPrevFrame.reset();}}else{ClearFrame(compositingFrameData.mRawData,compositingFrameData.mRect);}break;default:MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");caseDisposalMethod::NOT_SPECIFIED:caseDisposalMethod::KEEP:// Copy previous frame into compositingFrame before we put the new// frame on top// Assumes that the previous frame represents a full frame (it could be// smaller in size than the container, as long as the frame before it// erased itself)// Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)// will always be a valid frame number.if(mLastCompositedFrameIndex!=int32_t(aNextFrameIndex-1)){if(isFullPrevFrame&&!prevFrame->GetIsPaletted()){// Just copy the bitsCopyFrameImage(prevFrameData.mRawData,prevRect,compositingFrameData.mRawData,compositingFrameData.mRect);}else{if(needToBlankComposite){// Only blank composite when prev is transparent or not full.if(prevFrameData.mHasAlpha||!isFullPrevFrame){ClearFrame(compositingFrameData.mRawData,compositingFrameData.mRect);}}DrawFrameTo(prevFrameData.mRawData,prevFrameData.mRect,prevFrameData.mPaletteDataLength,prevFrameData.mHasAlpha,compositingFrameData.mRawData,compositingFrameData.mRect,prevFrameData.mBlendMethod,prevFrameData.mBlendRect);}}}}elseif(needToBlankComposite){// If we just created the composite, it could have anything in its// buffers. Clear themClearFrame(compositingFrameData.mRawData,compositingFrameData.mRect);}// Check if the frame we are composing wants the previous image restored after// it is done. Don't store it (again) if last frame wanted its image restored// tooif((nextFrameData.mDisposalMethod==DisposalMethod::RESTORE_PREVIOUS)&&(prevFrameData.mDisposalMethod!=DisposalMethod::RESTORE_PREVIOUS)){// We are storing the whole image.// It would be better if we just stored the area that nextFrame is going to// overwrite.if(!mCompositingPrevFrame){RefPtr<imgFrame>newFrame=newimgFrame;nsresultrv=newFrame->InitForAnimator(mSize,SurfaceFormat::B8G8R8A8);if(NS_FAILED(rv)){mCompositingPrevFrame.reset();returnfalse;}mCompositingPrevFrame=newFrame->RawAccessRef();}AnimationDatacompositingPrevFrameData=mCompositingPrevFrame->GetAnimationData();CopyFrameImage(compositingFrameData.mRawData,compositingFrameData.mRect,compositingPrevFrameData.mRawData,compositingPrevFrameData.mRect);mCompositingPrevFrame->Finish();}// blit next frame into it's correct spotDrawFrameTo(nextFrameData.mRawData,nextFrameData.mRect,nextFrameData.mPaletteDataLength,nextFrameData.mHasAlpha,compositingFrameData.mRawData,compositingFrameData.mRect,nextFrameData.mBlendMethod,nextFrameData.mBlendRect);// Tell the image that it is fully 'downloaded'.mCompositingFrame->Finish();mLastCompositedFrameIndex=int32_t(aNextFrameIndex);returntrue;}//******************************************************************************// Fill aFrame with black. Does also clears the mask.voidFrameAnimator::ClearFrame(uint8_t*aFrameData,constIntRect&aFrameRect){if(!aFrameData){return;}memset(aFrameData,0,aFrameRect.width*aFrameRect.height*4);}//******************************************************************************voidFrameAnimator::ClearFrame(uint8_t*aFrameData,constIntRect&aFrameRect,constIntRect&aRectToClear){if(!aFrameData||aFrameRect.width<=0||aFrameRect.height<=0||aRectToClear.width<=0||aRectToClear.height<=0){return;}IntRecttoClear=aFrameRect.Intersect(aRectToClear);if(toClear.IsEmpty()){return;}uint32_tbytesPerRow=aFrameRect.width*4;for(introw=toClear.y;row<toClear.y+toClear.height;++row){memset(aFrameData+toClear.x*4+row*bytesPerRow,0,toClear.width*4);}}//******************************************************************************// Whether we succeed or fail will not cause a crash, and there's not much// we can do about a failure, so there we don't return a nsresultboolFrameAnimator::CopyFrameImage(constuint8_t*aDataSrc,constIntRect&aRectSrc,uint8_t*aDataDest,constIntRect&aRectDest){uint32_tdataLengthSrc=aRectSrc.width*aRectSrc.height*4;uint32_tdataLengthDest=aRectDest.width*aRectDest.height*4;if(!aDataDest||!aDataSrc||dataLengthSrc!=dataLengthDest){returnfalse;}memcpy(aDataDest,aDataSrc,dataLengthDest);returntrue;}nsresultFrameAnimator::DrawFrameTo(constuint8_t*aSrcData,constIntRect&aSrcRect,uint32_taSrcPaletteLength,boolaSrcHasAlpha,uint8_t*aDstPixels,constIntRect&aDstRect,BlendMethodaBlendMethod,constMaybe<IntRect>&aBlendRect){NS_ENSURE_ARG_POINTER(aSrcData);NS_ENSURE_ARG_POINTER(aDstPixels);// According to both AGIF and APNG specs, offsets are unsignedif(aSrcRect.x<0||aSrcRect.y<0){NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");returnNS_ERROR_FAILURE;}// Outside the destination frame, skip itif((aSrcRect.x>aDstRect.width)||(aSrcRect.y>aDstRect.height)){returnNS_OK;}if(aSrcPaletteLength){// Larger than the destination frame, clip itint32_twidth=std::min(aSrcRect.width,aDstRect.width-aSrcRect.x);int32_theight=std::min(aSrcRect.height,aDstRect.height-aSrcRect.y);// The clipped image must now fully fit within destination image frameNS_ASSERTION((aSrcRect.x>=0)&&(aSrcRect.y>=0)&&(aSrcRect.x+width<=aDstRect.width)&&(aSrcRect.y+height<=aDstRect.height),"FrameAnimator::DrawFrameTo: Invalid aSrcRect");// clipped image size may be smaller than source, but not largerNS_ASSERTION((width<=aSrcRect.width)&&(height<=aSrcRect.height),"FrameAnimator::DrawFrameTo: source must be smaller than dest");// Get pointers to image dataconstuint8_t*srcPixels=aSrcData+aSrcPaletteLength;uint32_t*dstPixels=reinterpret_cast<uint32_t*>(aDstPixels);constuint32_t*colormap=reinterpret_cast<constuint32_t*>(aSrcData);// Skip to the right offsetdstPixels+=aSrcRect.x+(aSrcRect.y*aDstRect.width);if(!aSrcHasAlpha){for(int32_tr=height;r>0;--r){for(int32_tc=0;c<width;c++){dstPixels[c]=colormap[srcPixels[c]];}// Go to the next row in the source resp. destination imagesrcPixels+=aSrcRect.width;dstPixels+=aDstRect.width;}}else{for(int32_tr=height;r>0;--r){for(int32_tc=0;c<width;c++){constuint32_tcolor=colormap[srcPixels[c]];if(color){dstPixels[c]=color;}}// Go to the next row in the source resp. destination imagesrcPixels+=aSrcRect.width;dstPixels+=aDstRect.width;}}}else{pixman_image_t*src=pixman_image_create_bits(aSrcHasAlpha?PIXMAN_a8r8g8b8:PIXMAN_x8r8g8b8,aSrcRect.width,aSrcRect.height,reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),aSrcRect.width*4);if(!src){returnNS_ERROR_OUT_OF_MEMORY;}pixman_image_t*dst=pixman_image_create_bits(PIXMAN_a8r8g8b8,aDstRect.width,aDstRect.height,reinterpret_cast<uint32_t*>(aDstPixels),aDstRect.width*4);if(!dst){pixman_image_unref(src);returnNS_ERROR_OUT_OF_MEMORY;}// XXX(seth): This is inefficient but we'll remove it quite soon when we// move frame compositing into SurfacePipe. For now we need this because// RemoveFrameRectFilter has transformed PNG frames with frame rects into// imgFrame's with no frame rects, but with a region of 0 alpha where the// frame rect should be. This works really nicely if we're using// BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect// area overwriting the previous frame, which makes the animation look// wrong. This quick hack fixes that by first compositing the whle new frame// with BlendMethod::OVER, and then recopying the area that uses// BlendMethod::SOURCE if needed. To make this work, the decoder has to// provide a "blend rect" that tells us where to do this. This is just the// frame rect, but hidden in a way that makes it invisible to most of the// system, so we can keep eliminating dependencies on it.autoop=aBlendMethod==BlendMethod::SOURCE?PIXMAN_OP_SRC:PIXMAN_OP_OVER;if(aBlendMethod==BlendMethod::OVER||!aBlendRect||(aBlendMethod==BlendMethod::SOURCE&&aSrcRect.IsEqualEdges(*aBlendRect))){// We don't need to do anything clever. (Or, in the case where no blend// rect was specified, we can't.)pixman_image_composite32(op,src,nullptr,dst,0,0,0,0,aSrcRect.x,aSrcRect.y,aSrcRect.width,aSrcRect.height);}else{// We need to do the OVER followed by SOURCE trick above.pixman_image_composite32(PIXMAN_OP_OVER,src,nullptr,dst,0,0,0,0,aSrcRect.x,aSrcRect.y,aSrcRect.width,aSrcRect.height);pixman_image_composite32(PIXMAN_OP_SRC,src,nullptr,dst,aBlendRect->x,aBlendRect->y,0,0,aBlendRect->x,aBlendRect->y,aBlendRect->width,aBlendRect->height);}pixman_image_unref(src);pixman_image_unref(dst);}returnNS_OK;}}// namespace image}// namespace mozilla